/*
 *	Yalala
 *	
 *	Copyright © MediaMonks B.V.
 *	All rights reserved.
 *	
 *	Redistribution and use in source and binary forms, with or without
 *	modification, are permitted provided that the following conditions are met:
 *	1. Redistributions of source code must retain the above copyright
 *	   notice, this list of conditions and the following disclaimer.
 *	2. Redistributions in binary form must reproduce the above copyright
 *	   notice, this list of conditions and the following disclaimer in the
 *	   documentation and/or other materials provided with the distribution.
 *	3. All advertising materials mentioning features or use of this software
 *	   must display the following acknowledgement:
 *	   This product includes software developed by MediaMonks B.V.
 *	4. Neither the name of MediaMonks B.V. nor the
 *	   names of its contributors may be used to endorse or promote products
 *	   derived from this software without specific prior written permission.
 *	
 *	THIS SOFTWARE IS PROVIDED BY MEDIAMONKS B.V. ''AS IS'' AND ANY
 *	EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 *	WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 *	DISCLAIMED. IN NO EVENT SHALL MEDIAMONKS B.V. BE LIABLE FOR ANY
 *	DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 *	(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *	LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *	ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *	(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 *	SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *	
 */

package com.mediamonks.yalala.ui 
{
	import nl.acidcats.yalog.common.Levels;
	import nl.acidcats.yalog.common.MessageData;

	import temple.codecomponents.scroll.CodeScrollBar;
	import temple.common.enum.Orientation;
	import temple.ui.buttons.behaviors.ButtonBinder;
	import temple.ui.layout.liquid.LiquidContainer;
	import temple.ui.layout.liquid.LiquidSprite;

	import com.mediamonks.yalala.data.enum.RowStatus;
	import com.mediamonks.yalala.data.filters.CompositeFilter;
	import com.mediamonks.yalala.data.vo.RowData;
	import com.mediamonks.yalala.data.vo.SettingsData;
	import com.mediamonks.yalala.ui.table.Table;

	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.text.TextField;
	import flash.utils.Dictionary;
	import flash.utils.getTimer;

	/**
	 * @author Thijs Broerse
	 */
	public class Viewer extends LiquidContainer
	{
		/**
		 * Max duration of a single thread. The SWF is running on 31 fps, so 1 frame takes 1000/31 miliseconds.
		 * Thread may only take .6 of this time
		 */
		private static const _MAX_THREAD_DURATION:int = int((1000 / 31) * 1);
		
		/**
		 * Length of the first 'test' tread to calculate the maximum number of calculation during one thread
		 */
		private static const _TEST_THREAD_LENGTH:int = 1000;
		
		private static const _NO_RESULT_MESSAGE:RowData = new RowData(new MessageData("no messages found", Levels.STATUS));

		private var _table:Table;
		private var _verticalScrollBar:CodeScrollBar;
		private var _horizontalScrollBar:CodeScrollBar;
		private var _background:LiquidSprite;

		private var _messages:Vector.<MessageData>;
		private var _splitDict:Dictionary;
		private var _filter:CompositeFilter;
		private var _settings:SettingsData;
		private var _filtered:Vector.<MessageData>;
		private var _updateNeeded:Boolean;
		private var _updateIndex:uint;

		public function Viewer(settings:SettingsData)
		{
			_settings = settings;
			_messages = new Vector.<MessageData>();
			_filtered = new Vector.<MessageData>();
			_filter = new CompositeFilter();
			_splitDict = new Dictionary(true);
			
			createUI();
			
			addEventListener(Event.ENTER_FRAME, handleEnterFrame);
		}

		private function createUI():void 
		{
			// background
			_background = new LiquidSprite();
			_background.graphics.beginFill(0xFF0000, 0);
			_background.graphics.drawRect(0, 0, 1, 1);
			_background.graphics.endFill();
			_background.left = _background.right = _background.top = _background.bottom = 0;
			addChild(_background);
			
			// table
			_table = new Table();
			_table.top = 0;
			_table.left = 0;
			_table.right = 14;
			_table.bottom = 0;
			addChild(_table);
			_table.addEventListener(Event.RESIZE, handleTableResize);
			
			// vertical scrollbar
			_verticalScrollBar = new CodeScrollBar();
			_verticalScrollBar.top = 0;
			_verticalScrollBar.bottom = 14;
			_verticalScrollBar.right = 0;
			addChild(_verticalScrollBar);
			_verticalScrollBar.scrollPane = _table;

			// vertical scrollbar
			_horizontalScrollBar = new CodeScrollBar(Orientation.HORIZONTAL);
			_horizontalScrollBar.left = 0;
			_horizontalScrollBar.bottom = 0;
			_horizontalScrollBar.right = 14;
			addChild(_horizontalScrollBar);
			_horizontalScrollBar.scrollPane = _table;
			
			new ButtonBinder(_background, _table);
			
			//ScrollBar.scrollProxy = ScrollBehavior.scrollProxy = new TweenLitePropertyProxy(.1);
			
			addEventListener(MouseEvent.MOUSE_UP, handleMouseUp);
		}

		public function addMessage(messageData:MessageData):void 
		{
			_messages.push(messageData);
			if (!_updateNeeded)
			{
				_updateNeeded = true;
				filterMessages();
			}
		}
		
		public function updateAllMessages():void 
		{
			_updateNeeded = true;
			_updateIndex = 0;
			_filtered.length = 0;
			//_table.scrollV = 0;
		}
		
		private function handleEnterFrame(event:Event):void 
		{
			filterMessages();
		}

		private function filterMessages():void 
		{
			if (_updateNeeded && _updateIndex < _messages.length)
			{
				var time:int = getTimer();
				
				var leni:int = Math.min(_messages.length, _updateIndex + _TEST_THREAD_LENGTH);
				
				while (_updateIndex < leni)
				{
					if (_filter.isValid(_messages[_updateIndex]))
					{
						_filtered.push(_messages[_updateIndex]);
					}
					_updateIndex++;
				}
				
				if (_updateIndex < _messages.length)
				{
					var duration:int = getTimer() - time;
					
					if (duration < _MAX_THREAD_DURATION)
					{
						// we have some time left, calculate how many messages we can handle
						leni = (_MAX_THREAD_DURATION - duration) / (duration / _TEST_THREAD_LENGTH);
						leni = Math.min(leni, _messages.length);
						
						while (_updateIndex < leni)
						{
							if (_filter.isValid(_messages[_updateIndex]))
							{
								_filtered.push(_messages[_updateIndex]);
							}
							_updateIndex++;
						}
					}
				}
				
				var rows:Vector.<RowData> = new Vector.<RowData>();
				leni = _filtered.length;
				
				for (var i:int = 0; i < leni; i++)
				{
					rows = rows.concat(splitMessage(_filtered[i]));
				}
				
				if (!rows.length) rows.push(_NO_RESULT_MESSAGE);
				
				_table.data = rows;
				
				if (_updateIndex >= _messages.length) _updateNeeded = false;
				
				if (_settings.autoScroll)
				{
					_table.scrollVTo(_table.maxScrollV);
				}
			}
		}
		
		public function clear():void 
		{
			_messages.length = 0;
			_table.clear();
			updateAllMessages();
		}

		public function clearConnection(id:Number):void
		{
			for (var i:int = _messages.length - 1;i >= 0; --i)
			{
				if (_messages[i].connectionId == id) _messages.splice(i, 1);
			}
			updateAllMessages();
		}

		public function get filter():CompositeFilter
		{
			return _filter;
		}
		
		/**
		 *	Split message into several messages if message contains newline
		 */
		private function splitMessage(message:MessageData):Vector.<RowData> 
		{
			var splitted:Vector.<RowData> = new Vector.<RowData>();
			
			if (_splitDict[message])
			{
				return collapse(_splitDict[message]);
			}
			else if (message.text.indexOf("\n") > -1) 
			{
				var stringList:Array = message.text.split("\n");
				var len:Number = stringList.length;
				var parent:RowData, row:RowData, closeTag:String;
				for (var i:Number = 0;i < len;i++) 
				{
					// add new messages, set time only for first message, sender only for last message
					row = new RowData(new MessageData(stringList[i], message.level, (i == 0) ? message.time : null, (i < len - 1) ? null : message.sender, (i < len - 1) ? 0 : message.senderId, (i < len - 1) ? null : message.stackTrace, message.frame), message);
					row.parent = parent;
					
					// check if message is a '[' or a '{' (so is can be closed)
					if (row.message.text.match(/^(\s*)([\[{])$/gm).length)
					{
						parent = row;
						parent.children ||= new Vector.<RowData>();
						parent.status = RowStatus.OPENED;
						closeTag = parent.message.text;
						closeTag = closeTag.replace("[", "]").replace("{", "}");
					}
					else if (parent && row.message.text == closeTag)
					{
						parent = parent.parent;
						if (parent)
						{
							closeTag = parent.message.text;
							closeTag = closeTag.replace("[", "]").replace("{", "}");
						}
						else
						{
							closeTag = null;
						}
					}
					else if (parent) parent.children.push(row);
					
					splitted.push(row);
				}
			} 
			else 
			{
				splitted.push(new RowData(message));
			}
			_splitDict[message] = splitted;
			
			return splitted;
		}

		private function collapse(rows:Vector.<RowData>):Vector.<RowData> 
		{
			var collapsed:Vector.<RowData> = new Vector.<RowData>();
			var parent:RowData;
			for (var i:int = 0, leni:int = rows.length; i < leni; i++)
			{
				parent = rows[i].parent;
				
				while (parent && parent.isOpen) parent = parent.parent;
				
				if (!parent) collapsed.push(rows[i]);
			}
			return collapsed;
		}
		
		private function handleMouseUp(event:MouseEvent):void 
		{
			if (event.target is TextField)
			{
				var tf:TextField = TextField(event.target);
				var selection:String = tf.text.substring(tf.selectionBeginIndex, tf.selectionEndIndex);
				_table.highLight(selection);
			}
		}

		private function handleTableResize(event:Event):void 
		{
			_table.bottom = _table.contentWidth > _table.width ? 14 : 0;
		}
		
		public function get messages():Vector.<MessageData>
		{
			return _messages;
		}

		public function get filtered():Vector.<MessageData>
		{
			return _filtered;
		}
		
	}
}
